require 'find'
require 'fileutils'

class Obfuscator

  @@unique_name = "aaaa"
  
  def alreadyEncrypted(name)
    obfuscated = (name.length == 4 && name.to_i(36) < @@unique_name.to_i(36))
    if (obfuscated)
      puts("###################{name} already obfuscated###################")
    end
    return obfuscated
  end
  
  def renameGeneric(contents, pre_identifier, post_identifier)
    #Find private function declarations
    f_list = Hash.new()
    contents.each_line do |line|
      if (line.start_with?(pre_identifier))
        f_start = pre_identifier.length + 1
        f_end = line.index(post_identifier)
        f_name = line[f_start, f_end - f_start]
        
        if (!alreadyEncrypted(f_name))
          f_list[f_name] = @@unique_name
          @@unique_name = @@unique_name.succ
          puts("#{f_name} => #{f_list[f_name]}")
        end
        
      end
    end
    
    #Find calls to those functions and the declaration itself and replace
    f_list.each do |key, value|
      # matches function calls candidates
      regex = Regexp.new("\\b#{key}\\b")
      i = contents.index(regex)
      while i
        # discard functions preceeded by a dot.
        preceiding = contents[i-1,1]
        if (preceiding != "." && preceiding != '"' && preceiding != "'" && preceiding != "@")
          contents[i, key.length] = value # do replace
        end
        # except functions of this.
        preceiding = contents[i-5,5]
        if (preceiding == "this.")
          contents[i, key.length] = value # do replace
        end
        # search next function coincidence
        i = contents.index(regex, i+1)
      end
    end
      
    return contents
  end

  def renamePrivateFunctions(contents)
    contents = renameGeneric(contents, "private function", "(")
    return contents;    
  end

  def renamePrivateVariables(contents)
    contents = renameGeneric(contents, "private var", ":")
    return contents;
  end
  
  #devuelve un array con las funciones encontradas
  def retrieveFunctionsBody(contents)
    in_function = false
    brace_indent = 0
    in_function_body = false
    functionsArray = Array.new
    function_code = ""
    
    contents.each_line do |line|
      
      if (line.index(" function "))
        in_function = !in_function
      end
      
      if (!in_function)
        brace_indent = 0
      end
      
      if in_function
        function_code = function_code.concat(line)
        brace_indent += line.count("{")
        if (brace_indent > 0)
          in_function_body = true
        end
        brace_indent -= line.count("}")
        if (brace_indent <= 0 && in_function_body)
          #function end reached
          in_function = false
          in_function_body = false
          functionsArray = functionsArray.concat([function_code])          
          function_code = ""
        end
      end      
    end
    
    # Quita la definicion de la funcion para dejar solo el body
    answer = Array.new
    functionsArray.each do |f|
      f = f[f.index("{"), f.rindex("}")]
      answer.concat([f])
    end
    
    return answer
  end
  
  def renameFunctionVariables(contents)
    contents = renameGeneric(contents, "var", ":")
    contents = renameGeneric(contents, "Vector", ":")
    return contents
  end
  
  def renameLocalVariables(contents)
    f = retrieveFunctionsBody(contents)
    f.each do |function|
      contents[contents.index(function), function.length] = renameFunctionVariables(function)
    end
    return contents
  end

  def encrypt(path)
    puts "Encrypting file: #{path}"
    
    file = File.open(path, "rb")
    contents = "// File encrypted\n #{file.read}"
    file.close
    
    # removes /* */ style comments.
    regexp = /\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//m
    contents = contents.gsub(regexp, "")
    
    # removes // style comments.
    regexp = /\/\/.*$/
    contents = contents.gsub(regexp, "")
    
    #remove extra whitespaces
    #regexp = /\s{2,}/
    #contents = contents.gsub(regexp, "")
    
    
    
    # removes begin line whitespaces.
    regexp = /^\s+/
    contents = contents.gsub(regexp, "")
    
    # change local function variable names.
    # note that there may be variable names with the same name that the functions where they are:
    #
    # private function foo():void
    # {
    #   var foo:string = "hi!";
    #   trace(foo); // foo is the local one.
    # }
    #
    contents = renameLocalVariables(contents);
    
    # change private function names.
    contents = renamePrivateFunctions(contents)
    
    # change private class variables names.
    contents = renamePrivateVariables(contents)    
    
    # remove EOL
    #regexp = /;\s+/
    #contents = contents.gsub(";\n", ";")
    
    
    
    file = File.open(path, "wb")
    file.write(contents)
    file.flush
    file.close
  end

end # Obfuscator

def main
  
  # gets source path and encrypted path.
  source = ARGF.argv[0] ? ARGF.argv[0] : "src"
  encrypted_path = ARGF.argv[1] ? ARGF.argv[1] : "#{source}_encrypted"
  
  puts "\nEncrypting folder: #{source} to #{encrypted_path}\n"
  
  # Makes a copy of the original folder.
  FileUtils.rm_rf encrypted_path  
  FileUtils.cp_r source, encrypted_path
   
  # Traverses folder copy encrypting as files.
  obfuscator = Obfuscator.new
  Find.find(encrypted_path) do |path|    
    if !FileTest.directory?(path) && File.extname(path) == ".as"
      obfuscator.encrypt(path)
    end
  end
  
end

if __FILE__ == $0
  main  
end

